package com.ar.engine;

import java.io.IOException;
import java.io.InputStream;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.opengl.GLUtils;
import android.opengl.Matrix;
import android.util.Log;

import com.ar.engine.common.ESTransform;
import com.ar.engine.common.Shader;
import com.ar.engine.objLoader.Model;

public class EngineRenderer implements GLSurfaceView.Renderer{

    // Handle to a program object
    private int ProgramObject;

    // MVP matrix
    private ESTransform MVPMatrix = new ESTransform();
    ESTransform perspective;
    ESTransform modelview;
    
    //Matrices
    private float[] mMVPMatrix = new float[16];

    private float[] mScaleMatrix = new float[16];
    private float[] mRotXMatrix = new float[16];
    private float[] mRotYMatrix = new float[16];

    private float[] mMMatrix = new float[16];	
    private float[] mVMatrix = new float[16]; 	
    private float[] normalMatrix = new float[16];
    
    private float[] mTransMatrix = new float[16];

    
	// light parameters
	private float[] lightPos;
	private float[] lightColor;

	
	// material properties
	private float[] matAmbient;
	private float[] matDiffuse;
	private float[] matSpecular;
	private float matShininess;

	// eye position
	private float[] eyePos = {-5.0f, 0.0f, 0.0f};
	
	// Shader handlers
	private Shader _shaders[] = new Shader[3];
	
	Shader testing;
	
	/** Shader code **/
	private int[] vShaders;
	private int[] fShaders;

	
	// shader constants
	private final int GOURAUD_SHADER = 0;
	private final int PHONG_SHADER = 1;
	private final int NORMALMAP_SHADER = 2;
	
	// Light rotation Angle
	
	private float angle = 0.0f;
    
    //Scene controllers
    private float RotateX;
    private float RotateY;
    private float RotateZ;

	
    // Texture Data
    private int TextureID;

    
    // Context to access local resources
    private Context context;
    
    // Testing OBJ Loader	
	Model zippo;

	private int _currentShader;
    
	///
    // Constructor
    ///
    public EngineRenderer(Context context)
    {
    	this.context = context;
    	
    	// setup all the shaders
		vShaders = new int[3];
		fShaders = new int[3];

		// basic - just gouraud shading
		vShaders[GOURAUD_SHADER] = R.raw.gouraud_vs;
		fShaders[GOURAUD_SHADER] = R.raw.gouraud_ps;

		// phong shading
		vShaders[PHONG_SHADER] = R.raw.phong_vs;
		fShaders[PHONG_SHADER] = R.raw.phong_ps;

		// normal mapping
		vShaders[NORMALMAP_SHADER] = R.raw.normalmap_vs;
		fShaders[NORMALMAP_SHADER] = R.raw.normalmap_ps;

		// set current object and shader
		this._currentShader = this.NORMALMAP_SHADER;
    }

    ///
    // Initialize the shader and program object
    ///
    public void onSurfaceCreated(GL10 glUnused, EGLConfig config)
    {
    	
    	perspective = new ESTransform();
    	perspective.matrixLoadIdentity();
    	
     	zippo = new Model(R.raw.arbuilding, context);
    	// initialize shaders
		try {
			testing = new Shader(R.raw.phong_vs, R.raw.phong_ps, context, false, 0);
			
		} catch (Exception e) {
			Log.d("SHADER 0 SETUP", e.getLocalizedMessage());
		}

		GLES20.glEnable( GLES20.GL_DEPTH_TEST );
		GLES20.glClearDepthf(1.0f);
		GLES20.glDepthFunc( GLES20.GL_LEQUAL );
		GLES20.glDepthMask( true );

		// cull backface
		GLES20.glEnable( GLES20.GL_CULL_FACE );
		GLES20.glCullFace(GLES20.GL_BACK); 

		// light variables
//		float[] lightP = {0.0f, 25.0f, 60.0f, 1.0f}; // Dawn
//		float[] lightP = {0, 8.0f, 35.0f, 1.0f}; // Sun set
//		float[] lightP = {0.0f, 50.0f, 60.0f, 1.0f}; // Sun at day
		float[] lightP = {0.0f, 20.0f, 60.0f, 1.0f}; // Sun at day
		
		this.lightPos = lightP;

		float[] lightC = {1.0f, 1.0f, 1.0f , 1.0f};
		this.lightColor = lightC;

		// material properties
		float[] mA = {0f, 0f, 0f, 1.0f};
		matAmbient = mA;

//		float[] mD = {0.5f, 0.49f, 0.6f, 1.0f}; // Sunrise
//		float[] mD = {0.71f, 0.49f, 0.36f, 1.0f}; // Sunset
		float[] mD = {0.95f, 0.95f, 0.88f, 1.0f}; // Sun During day
//		float[] mD = {0f, 0f, 0f, 1.0f};
		matDiffuse = mD;

		float[] mS =  {0.0f, 0.0f, 0.0f, 1.0f};
		matSpecular = mS;

		matShininess = 5.0f;
		
		
		loadTextures();
		

		
		Matrix.setLookAtM(mVMatrix, 0, 0, 0, -25.0f, 0.0f, 0f, 0, 0f, 1.0f, 0.0f);
    }

    ///
    // Draw a triangle using the shader pair created in onSurfaceCreated()
    //
    public void onDrawFrame(GL10 glUnused)
    {
    	GLES20.glClearColor(0.0f, 0.0f, 0.0f, 0f);
		GLES20.glClear( GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);

		ProgramObject = testing.get_program();
		
		GLES20.glUseProgram(ProgramObject);
		
		// scaling
		Matrix.setIdentityM(mScaleMatrix, 0);
		Matrix.scaleM(mScaleMatrix, 0, 1, 1, 1);

		// Rotation along x
		Matrix.setRotateM(mRotXMatrix, 0, -10, 1.0f, 0.0f, 0.0f);
		Matrix.setRotateM(mRotYMatrix, 0, 145, 0.0f, 1.0f, 0.0f);
		
		Matrix.setIdentityM(mTransMatrix, 0);
		Matrix.translateM(mTransMatrix, 0, 0, -10, -5);

		// Set the ModelViewProjectionMatrix
		float tempMatrix[] = new float[16]; 
		Matrix.multiplyMM(tempMatrix, 0, mRotYMatrix, 0, mRotXMatrix, 0);
		Matrix.multiplyMM(mMMatrix, 0, mTransMatrix, 0, tempMatrix, 0);
		Matrix.multiplyMM(mMVPMatrix, 0, mVMatrix, 0, mMMatrix, 0);
		Matrix.multiplyMM(mMVPMatrix, 0, perspective.get(), 0, mMVPMatrix, 0);

		// send to the shader
		GLES20.glUniformMatrix4fv(GLES20.glGetUniformLocation(ProgramObject, "uMVPMatrix"), 1, false, mMVPMatrix, 0);

		// Create the normal modelview matrix
		// Invert + transpose of mvpmatrix
		Matrix.invertM(normalMatrix, 0, mMVPMatrix, 0);
		Matrix.transposeM(normalMatrix, 0, normalMatrix, 0);

		// send to the shader
		GLES20.glUniformMatrix4fv(GLES20.glGetUniformLocation(ProgramObject, "normalMatrix"), 1, false, mMVPMatrix, 0);

		// lighting variables
		// send to shaders
		GLES20.glUniform4fv(GLES20.glGetUniformLocation(ProgramObject, "lightPos"), 1, lightPos, 0);
		GLES20.glUniform4fv(GLES20.glGetUniformLocation(ProgramObject, "lightColor"), 1, lightColor, 0);

		// material 
		GLES20.glUniform4fv(GLES20.glGetUniformLocation(ProgramObject, "matAmbient"), 1, matAmbient, 0);
		GLES20.glUniform4fv(GLES20.glGetUniformLocation(ProgramObject, "matDiffuse"), 1, matDiffuse, 0);
		GLES20.glUniform4fv(GLES20.glGetUniformLocation(ProgramObject, "matSpecular"), 1, matSpecular, 0);
		GLES20.glUniform1f(GLES20.glGetUniformLocation(ProgramObject, "matShininess"), matShininess);

		// eye position
		GLES20.glUniform3fv(GLES20.glGetUniformLocation(ProgramObject, "eyePos")/*shader.eyeHandle*/, 1, eyePos, 0);
		
		GLES20.glUniform1f(GLES20.glGetUniformLocation(ProgramObject, "hasTexture")/*shader.hasTextureHandle*/, 1.0f);
		
		GLES20.glVertexAttribPointer(GLES20.glGetAttribLocation(ProgramObject, "aPosition"), 3, GLES20.GL_FLOAT, false,
				0, zippo.getVertices());
		GLES20.glEnableVertexAttribArray(GLES20.glGetAttribLocation(ProgramObject, "aPosition"));
		
		GLES20.glVertexAttribPointer(GLES20.glGetAttribLocation(ProgramObject, "aNormal"), 3, GLES20.GL_FLOAT, false,
				0, zippo.getNormals());
		GLES20.glEnableVertexAttribArray(GLES20.glGetAttribLocation(ProgramObject, "aNormal"));
		
		GLES20.glVertexAttribPointer(GLES20.glGetAttribLocation(ProgramObject, "textureCoord"), 2, GLES20.GL_FLOAT, false,
				0, zippo.getTexCoords());
		GLES20.glEnableVertexAttribArray(GLES20.glGetAttribLocation(ProgramObject, "textureCoord"));
	
		GLES20.glDrawElements(GLES20.GL_TRIANGLES, zippo.getIndices().capacity(), GLES20.GL_UNSIGNED_SHORT, zippo.getIndices());
    }

    ///
    // Handle surface changes
    //
    public void onSurfaceChanged(GL10 glUnused, int width, int height)
    {
		GLES20.glViewport(0, 0, width, height);
		float ratio = (float) width / height;
		
		perspective.perspective(60, ratio, 1, 100);
    }

    public void setOrientation(float orientationX, float orientationY, float orientationZ)
    {
    	
    }
    
    private void loadTextures()
    {
		int[] textures = new int[1];
    	
    	GLES20.glGenTextures(1, textures, 0);
    	
    	TextureID = textures[0];
    	
		GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, TextureID);
    	GLES20.glActiveTexture(GLES20.GL_TEXTURE0 + 0);
    	GLES20.glUniform1i(GLES20.glGetUniformLocation(ProgramObject, "texture1"), 0);
    	
    	GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
        
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_REPEAT);
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_REPEAT);
        
        InputStream is = context.getResources().openRawResource(R.raw.aruildingtexture);
        
        Bitmap bitmap;
        try
        {
        	android.graphics.Matrix flip = new android.graphics.Matrix();
        	flip.postScale(1f, -1f);
        	Bitmap temp = BitmapFactory.decodeStream(is);
        	
        	bitmap = Bitmap.createBitmap(temp, 0, 0, temp.getWidth(), temp.getHeight(), flip, true);
        	
        }finally{
        	try{
        		is.close();
        	}catch (IOException e){
        		
        	}
        }
        
        GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
        bitmap.recycle();
    }
    
	public void setRotation(float accelX, float accelY, float accelZ) {
		this.RotateX = accelX;
		this.RotateY = accelY;
		this.RotateZ = accelZ;
	}
}